Shell学习笔记06-Shell脚本的条件测试与比较

GO


1. Shell脚本的条件测试

1.1. 条件测试方法综述

通常,在bash的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果执行不同的操作,有时也会与if等条件语句相结合,来完成测试判断,以减少程序运行的错误。

执行条件测试表达式后通常会返回,就像执行命令后的返回值为0表示真,非0表示假一样。

在bash编程里,条件测试常用的语法形式见下表:

条件测试语法 说明
语法1:test <测试表达式> 这是利用test命令进行条件测试表达式的方法。test命令和<测试表达式>之间至少有一个空格
语法2:[<测试表达式>] 这是通过[](单中括号)进行条件测试表达式的方法,和test命令的用法相同,这是推荐使用的方法。[]的边界和内容之间至少有一个空格
语法3:[[<测试表达式>]] 这是通过[[]](双中括号)进行条件测试表达式的方法,是比test[]更新的语法格式。[[]]的边界和内容之间至少有一个空格
语法4:((<测试表达式>)) 这是通过(())(双小括号)进行条件测试表达式的方法,一般用于if语句里。(())(双小括号)两端不需要有空格。

上表中有几个注意事项需要说明一下:

  • 语法1中的test命令和语法2中的[]是等价的。语法3中的[[]]为扩展的test命令,语法4中的(())常用于计算。建议以后使用相对友好的语法2,即中括号[]的语法格式。
  • [[]](双中括号)中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。
  • &&||><等操作符可以应用于[[]]中,但不能应用于[]中,在[]中一般用-a-o-gt(用于整数)、-lt(用于整数)代替上述操作符。
  • 对于整数的关系运算,也可以使用Shell的算术运算符(())

1.2. test条件测试的简单语法及示例

test条件测试的语法格式:test <测试表达式>

对于如下语句:test -f file && echo true || echo false。该语句表示如果file文件存在,则输出true,否则(||)输出false。这里的&&是并且的意思。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的,命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。

test命令测试表达式的逻辑也可以用上述表达式的一般逻辑(即仅有一个&&或||)来测试,示例如下:

1
2
test -f filename && echo 1 #<==若表达式成功,则输出1
test -f filename || echo 0 #<==若表达式不成功,则输出0

另外,逻辑操作符&&||的两端既可以有空格,也可以无空格,这就要看个人的编程习惯了。

范例:在test命令中使用-f选项(文件存在且为普通文件则表达式成立)测试文件:

1
2
3
4
5
[root@theshu ~]# test -f file && echo true || echo false
false
[root@theshu ~]# touch file
[root@theshu ~]# test -f file && echo true || echo false
true

范例:在test命令中使用-z选项(如果测试字符串的长度为0,则表达式成立)测试字符串:

1
2
3
4
5
6
7
8
[root@theshu ~]# test -z "theshu" && echo 1 || echo 0
0
[root@theshu ~]# char="theshu"
[root@theshu ~]# test -z "$char" && echo 1 || echo 0
0
[root@theshu ~]# char=""
[root@theshu ~]# test -z "$char" && echo 1 || echo 0
1

提示:关于test测试表达式的更多知识可执行man test查看帮助,但大部分场景都会使用[]的语法替代test命令的语法。

结论:test命令测试的功能很强大,但是和[][[]]的功能有所重合,因此,在实际工作中选择一种适合自己的语法就好了。对于其它的语法,能读懂别人写的脚本就可以了。

1.3. [](中括号)条件测试语法及示例

[]条件测试的语法格式为:[ <测试表达式> ]

注意:中括号内部的两端要有空格,[]test等价,即test的所有判断选择都可以直接在[]里使用。

对于如下语句:[ -f /tmp/theshu.txt ] && echo 1 || echo 0。如果/tmp/theshu.txt文件存在,则输出1,否则(||)输出0.这里的&&表示并且。[]的应用同test命令,若中括号里的命令执行成功(返回真),则执行&&后面的命令,否则执行||后面的命令。

[]测试表达式的逻辑也可以用如下的语法来判断逻辑的表达式写法(test命令的用法也适合于此),即:

1
2
[ -f /tmp/theshu.txt ] && echo 1 #<==若表达式成功,则输出1
[ -f /tmp/theshu.txt ] || echo 0 #<==若表达式不成功,则输出0

另外,逻辑操作符&&||的两端可以有空格也可以无空格。

示例:利用[]加-f选项(文件存在且为普通文件则表达式成立)测试文件:

1
2
3
4
5
6
7
8
9
[root@theshu ~]# [ -f /tmp/theshu.txt ] && echo 1 || echo 0
0
[root@theshu ~]# touch /tmp/theshu.txt
[root@theshu ~]# [ -f /tmp/theshu.txt ] && echo 1 || echo 0
1
[root@theshu ~]# [ -f /tmp/theshu.txt ] && echo 1
1
[root@theshu ~]# [ -f /tmp/theshu1.txt ] || echo 0
0

提示:[]命令的选项和test命令的选项是通用的,因此,使用[]时的参数选项可以通过man test命令获取帮助。

1.4. [[]] 条件测试语法及示例

[[]]条件测试的语法格式为:[[ <测试表达式> ]]。注意:双括号里的两端也要有空格。

对于如下语句:[[ -f /tmp/theshu.txt ]] && echo 1 || echo 0。如果/tmp/theshu.txt文件存在就输出1,否则(||)就输出0.这里的&&表示并且。[[]]的应用属于[]test命令的扩展命令,功能更丰富也更复杂。如果双括号里的表达式成立(为真),则执行&&后面的名利个,否则执行||后面的命令。

[[]]测试表达式的逻辑也可以使用如下的部分逻辑形式,即:

1
2
[[ -f /tmp/theshu.txt ]] && echo 0 #<==若表达式成功则输出1
[[ -f /tmp/theshu.txt ]] || echo 1 #<==若表达式不成功则输出0

另外,逻辑操作符&&||的两端可以有空格也可以无空格。

双中括号内部的两端要有空格,[[]]里的测试判断选项,也可以通过man test来获得,[[]]表达式与[]test用法的选项部分是相同的,其与[]test测试表达式的区别在于,在[[]]中可以使用通配符等进行模式匹配;并且&&||><等操作符可以用用[[]]中,但不能应用于[]中,在[]中一般使用-a-o-gt(用于整数)、-lt(用于整数)等操作符代替上文提到的用于[[]]``中的符号。除了使用通配符功能之外,建议放弃这个双括号的写法,虽然它是较新的test`命令的语法格式。

范例:[[]]的使用示例:

1
2
3
4
[root@theshu ~]# [[ -f /tmp/theshu.txt ]] || echo 0
0
[root@theshu ~]# touch /tmp/theshu.txt
[root@theshu ~]# [[ -f /tmp/theshu.txt ]] || echo 0

有关test[][[]]这些操作符的用法,通过help testman test查询即可得到帮助,完整的[][[]]用法可通过man bash来获取。

2. 文件测试表达式

2.1. 文件测试表达式的用法

在书写文件测试表达式时,通常可以使用下表中的文件测试操作符:

常用文件测试操作符 说明
-d 文件,d为directory 文件存在且为目录则为真,即测试表达式成立
-f 文件,f为file 文件存在且为普通文件则为真,即测试表达式成立
-e 文件,e为exist 文件存在则为真,即测试表达式成立。注意区别与-f-e不辨别是目录还是文件
-r 文件,r为read 文件存在且可读则为真,即测试表达式成立
-s 文件,s为size 文件存在且文件大小不为0则为真,即测试表达式成立
-w 文件,w为write 文件存在且可写则为真,即测试表达式成立
-x 文件,x为executable 文件存在且可执行则为真,即测试表达式成立
-L 文件,L为link 文件存在且为链接文件则为真,即测试表达式成立
f1 -nt f2,nt为newer than 文件f1比文件f2新则为真,即测试表达式成立。根据文件的修改时间来计算
f1 -ot f2,or为older than 文件f1比文件f2旧则为真,即测试表达式成立。根据文件的修改时间来计算

上表列出的是企业里比较常用的操作符,这些操作符对于[[]][]test的测试表达式几乎是通用的,更多的操作符可通过man test获得帮助。

2.2. 文件测试表达式举例

2.2.1. 普通文件测试表达式示例

  • 普通文件(测试文件类型)

    1
    2
    3
    4
    5
    [root@theshu ~]# touch theshu
    [root@theshu ~]# ls -l theshu
    -rw-r--r-- 1 root root 0 Feb 27 11:43 theshu
    [root@theshu ~]# [ -f theshu ] && echo 1 || echo 0
    1
  • 目录文件(测试文件类型)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@theshu ~]# touch theshu
    [root@theshu ~]# mkdir test
    [root@theshu ~]# [ -f test ] && echo 1 || echo 0
    0
    [root@theshu ~]# [ -e test ] && echo 1 || echo 0
    1
    [root@theshu ~]# [ -d test ] && echo 1 || echo 0
    1
    [root@theshu ~]# [ -d teshu ] && echo 1 || echo 0
    0

2.2.2. 测试文件属性示例

  • 范例:文件属性条件表达式测试实践:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [root@theshu ~]# ll theshu
    -rw-r--r-- 1 root root 0 Feb 27 11:43 theshu #<==文件权限默认为644
    [root@theshu ~]# [ -r theshu ] && echo 1 || echo 0
    1 #<==测试theshu是否可读
    [root@theshu ~]# [ -w theshu ] && echo 1 || echo 0
    1 #<==测试theshu是否可写
    [root@theshu ~]# [ -x theshu ] && echo 1 || echo 0
    0 #<==测试theshu是否可执行,输出为0,因为用户权限为没有x
    [root@theshu ~]# chmod 001 theshu #<==修改theshu的权限位为001
    [root@theshu ~]# ll theshu
    ---------x 1 root root 0 Feb 27 11:43 theshu #<==修改后的结果
    [root@theshu ~]# [ -w theshu ] && echo 1 || echo 0
    1 #<==用户权限位命名没有w,为什么还是返回1呢?
    [root@theshu ~]# echo 'echo test' > theshu #<==因为确实是可以写,这是root用户比较特殊的地方
    [root@theshu ~]# [ -r theshu ] && echo 1 || echo 0
    1 #<==用户权限位明明没有r,为什么还是返回1呢?
    [root@theshu ~]# cat theshu
    echo test #<==确实是可以读,这是root用户比较特殊的地方
    [root@theshu ~]# [ -x theshu ] && echo 1 || echo 0
    1
    [root@theshu ~]# ./theshu #<==可执行
    test

提示:测试文件的读、写、执行等属性,不光是根据文件属性rwx的标识来判断,还要看当前执行测试的用户是否真的可以按照对应的权限操作该文件。

2.2.3. 测试Shell变量示例

  • 首先定义file1file2两个变量,并分别赋予这两个变量对应的系统文件路径及文件名的值,如下:

    1
    2
    3
    [root@theshu ~]# file1=/etc/services ; file2=/etc/rc.local
    [root@theshu ~]# echo $file1 $file2
    /etc/services /etc/rc.local
  • 范例:对单个文件变量进行测试:

    1
    2
    3
    4
    5
    6
    7
    8
    [root@theshu ~]# [ -f "$file1" ] && echo 1 || echo 0
    1 #<==文件存在且为普通文件,所以为真(1
    [root@theshu ~]# [ -d "$file2" ] && echo 1 || echo 0
    0 #<==是文件而不是目录,所以为假(0
    [root@theshu ~]# [ -s "$file1" ] && echo 1 || echo 0
    1 #<==文件存在且大小不为0,所有为真(1
    [root@theshu ~]# [ -e "$file1" ] && echo 1 || echo 0
    1 #<==文件存在,所以为真(1
  • 范例:对单个目录或文件进行测试:

    1
    2
    3
    4
    5
    6
    7
    8
    [root@theshu ~]# [ -e /etc ] && echo 1 || echo 0
    1
    [root@theshu ~]# [ -w /etc/services ] && echo 1 || echo 0
    1
    [root@theshu ~]# su - theshu #<==切换到普通用户
    Last login: Tue Feb 27 10:35:07 CST 2018 from 221.199.162.130 on pts/0
    [theshu@theshu ~]$ [ -w /etc/services ] && echo 1 || echo 0
    0 #<==文件不可写,所以返回0
  • 测试时变量的特殊写法级问题

    • []测试变量时,如果被测试的变量不加双引号,那么测试结果可能会是不正确的,示例如下:

      1
      2
      3
      4
      5
      6
      [root@theshu ~]# echo $theshu
      #<==假设theshu变量没有被定义
      [root@theshu ~]# [ -f $theshu ] && echo 1 || echo 0
      1 #<==不加引号测试变量,逻辑结果错误
      [root@theshu ~]# [ -f "$theshu" ] && echo 1 || echo 0
      0 #<==加引号后测试变量,结果正确
    • 如果是文件实体路径,那么加引号与不加引号的结果是一样的:

      1
      2
      3
      4
      [root@theshu ~]# [ -f "/etc/services" ] && echo 1 || echo 0
      1 #<==加引号测试,结果正确
      [root@theshu ~]# [ -f /etc/services ] && echo 1 || echo 0
      1 #<==不加引号测试,结果也正确
    • 范例:在生产环境下,系统NFS启动脚本(/etc/init.d/nfs)的条件测试:

      1
      2
      3
      4
      5
      # Source networking configuration.
      [ -f /etc/sysconfig/network ] && . /etc/sysconfig/network
      # Check for and source configuration file otherwise set defaults
      [ -f /etc/sysconfig/nfs ] && . /etc/sysconfig/nfs

特别提示:系统脚本是我们学习编程的第一标杆,新手要多多参考脚本来学习,虽然有些脚本也不是特别规范。

  • 范例:实现系统bind启动脚本named(bind DNS服务):
    1
    2
    3
    4
    [ -r /etc/sysconfig/network ] && . /etc/sysconfig/network
    #<==若文件存在且可读,则加载/etc/sysconfig/network
    [ -x /usr/sbin/$named ] || exit 5
    #<==如果/usr/sbin/$named不可执行,则退出

特别提示:前面所将的都是[ -f /etc ] && echo 1 || echo 0的用法,bind启动脚本[ -x /usr/sbin/$named ] || exit 5的用法更值得注意,这里只用了一部分判断,结果却更简洁。

  • 范例:写出简单高效的测试文件
    • 在做测试判断时,不一定非得按照:前面的操作成功了如何,否则如何的方法来进行。直接做部分判断,有时看起来更简洁。例如:
      1
      2
      [ -x theshu ] && echo 1
      [ -f /etc] || echo 0

2.3. 特殊条件测试表达式案例

以下写法适用于所有的条件测试表达式,是工作中比较常用的替代if语句的方法。判断条件测试表达式的条件成立或不成立后,还需要继续执行多条命令语句的语法形式如下。

例如:当条件1成立时,同时执行命令1、命令2、命令3.不用if测试表达式的格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#<==第一种测试
[ 条件1 ] && {
命令1
命令2
命令3
}
#<==第二种测试
[[ 条件1 ]] && {
命令1
命令2
命令3
}
#<==第三种测试
test 条件1 && {
命令1
命令2
命令3
}

上面的判断相当于下面的if语句的效果:

1
2
3
4
5
6
if [ 条件1 ]
then
命令1
命令2
命令3
fi

上述的结构是当满足条件执行大括号里面的命令,如果是当条件不满足时才执行大括号里面的命令,那就要把&&换成||

范例:当条件不成立时,执行大括号里的多条命令,这里要使用逻辑操作符||

1
2
3
4
5
6
7
8
9
10
[root@theshu ~]# cat test123.sh
[ -f /etc ] || { #<==如果/etc/是普通文件不成立,则执行大括号里的命令集合
echo 1
echo 2
echo 3
}
[root@theshu ~]# sh test123.sh
1
2
3

如果把上述脚本写在一行里面,那么里面的每个命令都需要用分号结尾,示例如下:

1
2
3
[root@theshu ~]# [ -f /etc/services ] && { echo "I am theshu"; echo "I am goodman"; }
I am theshu
I am goodman

3. 字符串测试表达式

3.1. 字符串测试操作符

字符串测试操作符的作用包括:比较两个字符串是否相同、测试字符串的长度是否为零、字符串是否为NULL等。(NULL是指通过bash区分零长度字符串和空字符串)。

在书写测试表达式时,可以使用下表中的字符串测试操作符:

常用字符串测试操作符 说明
-n "字符串" 若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为no zero
-z "字符串" 若字符串的长度为0,则为真,即测试表达式成立,z可以理解为zero的缩写
"串1"="串2" 若字符串1等于字符串2,则为真,即测试表达式成立,可使用==代替=
"串1"!="串2" 若字符串1不等于字符串2,则为真,即测试表达式成立,但不能用!==代替!=

以下是针对字符串测试操作符的提示:

  • 对于字符串的测试,一定要将字符串加双引号之后再进行比较。如[ -n "$myvar" ],特别是使用[]的场景。
  • 比较符号(例如=!=)的两端一定要有空格。
  • !==可用于比较两个字符串是否相同。

字符串比较时的两个错误用法:

  • 字符串比较是若等号两端没有空格,则会导致判断出现逻辑错误,即使语法没问题,但是结果依然可能不对。示例如下:

    1
    2
    3
    4
    [root@theshu ~]# [ "abc"="1" ] && echo 1 || echo 0
    1 #<==若等号两端不带空格,则会出现明显的逻辑错误
    [root@theshu ~]# [ "abc" = "1" ] && echo 1 || echo 0
    0 #<==带空格的就是准确的
  • 字符串不加双引号,可能会导致判断上出现逻辑错误,即使语法没问题,但是结果依然可能不对。示例如下:

    1
    2
    3
    4
    5
    6
    7
    [root@theshu ~]# var="" #<==将变量内容置空
    [root@theshu ~]# [ -n "$var" ] && echo 1 || echo 0 #<==有双引号
    0 #<==给变量加双引号,返回0,-n不为空时为真,因为变量内容为空吗,因此输出0是对的。
    [root@theshu ~]# [ -n $var ] && echo 1 || echo 0 #<==去掉双引号
    1 #<==同样的表达式,不加引号和加双引号后测试的结果相反,可见加双引号的重要性
    [root@theshu ~]# [ -z "$var" ] && echo 1 || echo 0
    1 #<==如果字符串长度为0,则输出1,否则输出0

3.2. 字符串测试生产案例

范例:有关双引号和等号两端空格的生产系统标准:

1
2
3
4
[root@theshu ~]# sed -n '30,31p' /etc/init.d/network #<==系统网卡启动脚本案例
# Check that networking is up.
[ "${NETWORKING}" = "no" ] && exit 6
#<==字符串变量和字符串都加了双引号,比较符号两端也都有空格

4. 整数二元比较操作符

4.1. 整数二元比较操作符介绍

在书写测试表达式时,可以使用下面表中的整数二元比较操作符:

[]test中使用 (())[[]]中使用 说明
-eq === 相等,equal
-ne != 不想等,not equal
-gt > 大于,greater than
-ge >= 大于等于,greater equal
-lt < 小于
le <= 小于等于,less equal

以下是针对上述符号的特别说明:

  • =!=也可以在[]中做比较使用,但在[]中使用包含><的符号时,需要用反斜杠转义,有时不转义虽然语法不会报错,但是结果可能会不对。
  • 也可以在[[]]中使用包含-gt-lt的符号,但是不建议这样使用
  • 比较符号两端也要有空格

范例:二元数字在[]中使用<>非标准符号的比较:

1
2
3
4
5
6
7
8
9
10
11
12
[root@theshu ~]# [ 2 > 1 ] && echo 1 || echo 0
1
[root@theshu ~]# [ 2 < 1 ] && echo 1 || echo 0
1 #<==这里的逻辑结果不对,条件不成立,则应该返回0,可见,<操作符在[]里使用时会带来问题
[root@theshu ~]# [ 2 \< 1 ] && echo 1 || echo 0
0 #<==转义后这里是正确的
[root@theshu ~]# [ 2 = 1 ] && echo 1 || echo 0
0 #<==比较相等符号是正确的
[root@theshu ~]# [ 2 = 2 ] && echo 1 || echo 0
1 #<==比较相等符号是正确的
[root@theshu ~]# [ 2 != 2 ] && echo 1 || echo 0
0 #<==比较不相等符号也是正确的

对于比较符号的应用,建议尽可能地按照上面表格中标记的方法来使用,以避免出现逻辑错误。

范例:二元数字在[]中使用-gt-le类符号的比较:

1
2
3
4
5
6
7
8
[root@theshu ~]# [ 2 -gt 1 ] && echo 1 || echo 0
1 #<==2大于1成立,输出1
[root@theshu ~]# [ 2 -ge 1 ] && echo 1 || echo 0
1 #<==2大于等于1成立,输出1
[root@theshu ~]# [ 2 -le 1 ] && echo 1 || echo 0
0 #<==2小于等于1不成立,输出0
[root@theshu ~]# [ 2 -lt 1 ] && echo 1 || echo 0
0 #<==2小于1不成立,输出0

范例:二元数字配合不同种类的操作符在[[]]中的比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@theshu ~]# [[ 5 > 6 ]] && echo 1 || echo 0
0 #<==5大于6不成立,输出0
[root@theshu ~]# [[ 5 < 6 ]] && echo 1 || echo 0
1 #<==5小于6成立,输出1
[root@theshu ~]# [[ 5 != 6 ]] && echo 1 || echo 0
1 #<==5不等于6成立,输出1
[root@theshu ~]# [[ 5 = 6 ]] && echo 1 || echo 0
0 #<==5等于6不成立,输出0
[root@theshu ~]# [[ 5 -gt 6 ]] && echo 1 || echo 0
0 #<==5大于6不成立,输出0
[root@theshu ~]# [[ 5 -lt 6 ]] && echo 1 || echo 0
1 #<==5小于6成立,输出0
[root@theshu ~]# [[ 65 > 66 ]] && echo 1 || echo 0
0 #<==65大于66不成立,输出0
[root@theshu ~]# [[ 65 < 66 ]] && echo 1 || echo 0
1 #<==65小于66成立,输出1
[root@theshu ~]# [[ 65 = 66 ]] && echo 1 || echo 0
0 #<==65等于66不成立,输出0

提示:[[]]是扩展的test命令,其语法更丰富也更复杂。对于实际工作中的常规比较,不建议使用[[]],会给Shell学习带来很多麻烦,除非是特殊的正则匹配等,在[]无法使用的场景下才hi考虑使用[[]]

范例:二元数字在(())中的比较:

1
2
3
4
5
6
7
8
9
10
11
[root@theshu ~]# ((3>2)) && echo 1 || echo 0
1 #<==3大于2成立,输出1
[root@theshu ~]# ((3<2)) && echo 1 || echo 0
0 #<==3小于2不成立,输出0
[root@theshu ~]# ((3==2)) && echo 1 || echo 0
0 #<==3等于2不成立,输出0
[root@theshu ~]# ((3!==2)) && echo 1 || echo 0
-bash: ((: 3!==2: syntax error: operand expected (error token is "=2")
0 #<== !==符号不可用,语法错误
[root@theshu ~]# ((3!=2)) && echo 1 || echo 0
1 #<==3不等于2成立,输出1

有关[][[]](())用法的小结:

  • 整数加双引号的比较是对的
  • [[]]中用类似-eq等的写法是对的,[[]]中用类似><的写法也可能不对,有可能会只比较第一位,逻辑结果不对
  • []中用类似><的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=!=正确比较
  • (())中不能使用类似-eq等的写法,可以使用类似><的写法

提示:对于工作场景中的整数比较,推荐使用[](类似-eq的用法),当然使用(())的写法也是可以的。(只需要养成一个习惯即可)

4.2. 整数变量测试实践示例

范例:通过[]实现整数条件测试

1
2
3
4
5
6
7
[root@theshu ~]# a1=98;a2=99
[root@theshu ~]# [ $a1 -eq $a2 ] && echo 1 || echo 0
0 #<==测试$a1是否等于$a2
[root@theshu ~]# [ $a1 -gt $a2 ] && echo 1 || echo 0
0 #<==测试$a1是否大于$a2
[root@theshu ~]# [ $a1 -lt $a2 ] && echo 1 || echo 0
1 #<==测试$a1是否小于$a2

提示:有关整数(要确认是整数,否则hi报错)大小的比较,推荐使用本例中的方法。

范例:利用[[]](())实现直接通过常规数学运算符进行比较:

1
2
3
4
5
6
7
8
9
[root@theshu ~]# a1=98;a2=99
[root@theshu ~]# [[ $a1 > $a2 ]] && echo 1 || echo 0
0 #<==测试$a1是否大于$a2,尽量不用此写法
[root@theshu ~]# [[ $a1 < $a2 ]] && echo 1 || echo 0
1 #<==测试$a1是否小于$a2,尽量不用此写法
[root@theshu ~]# (($a1>=$a2)) && echo 1 || echo 0
0 #<==测试$a1是否大于等于$a2,此写法也可以
[root@theshu ~]# (($a1<=$a2)) && echo 1 || echo 0
1 #<==测试$a1是否小于等于$a2,此写法也可以

有关整数(要确认是整数,否则会报错)的大小比较,(())语法要优于[[]],但还是推荐优先使用[],次选是(()),不推荐使用[[]]。示例如下:

1
2
[ $num1 -eq $num2 ] #<==注意比较符号两边的空格和比较符号的写法,必须要有空格
(($sum1>$sum2)) #<==比较符号两边无需空格(多空格也可),使用常规数学的比较符号即可

5. 逻辑操作符

5.1. 逻辑操作符介绍

在书写测试表达式时,可以使用下表中的逻辑操作符实现复杂的条件测试:

[]test中使用 [[]](())中使用 说明
-a && and,与,两端都为真,则结果为真
-o 双竖线 or,或,两端有一个为真,则结果为真
! ! not,非,两端相反,则结果为真

对于上述操作符,有如下提示:

  • 逻辑操作符前后的表达式是否成立,一般用真假来表示
  • !的中文意思是,即与一个逻辑值相反的逻辑值‘
  • -a的中文意思是and&&),前后两个逻辑值都为,综合返回值才为,反之为
  • -o的中文意思是or||),前后两个逻辑值只要有一个为,,返回值就为
  • 连接两含[]test[[]]的表达式可用&&||

逻辑操作符运算规则:

  • -a&&的运算规则:只有逻辑操作符两端的表达式都成立时才为真;真(true)表示成立,对应的数字为1;假(false)表示不成立,对应的数字为0,这一点相当于如下表达式:

    1
    2
    3
    4
    [root@theshu ~]# [ -f /etc/hosts -a -f /etc/services ] && echo 1 || echo 0
    1 #<==单中括号文件测试
    [root@theshu ~]# [[ -f /etc/hosts && -f /etc/services ]] && echo 1 || echo 0
    1 #<==双中括号文件测试
  • 使用-a&&的总和表达式结果,相当于将两端表达式结果的对应数字(0或1)相乘:

    1
    2
    3
    4
    and结果1*0=0
    and结果0*1=0
    and结果1*1=1
    and结果0*0=0
  • 结论:and(&&)也称为,只有两端都是1时才为真,相当于取前后表达式的交集

  • -o||两端都是0才为假,任何一端不为0就是真,这相当于将两边表达式结果的对应数字(0或1)相加,对应的表达式为:

    1
    2
    3
    4
    [root@theshu ~]# [ 5 -eq 6 -o 5 -gt 3 ] && echo 1 || echo 0
    1
    [root@theshu ~]# ((5==6||5>3)) && echo 1 || echo 0
    1
  • -o||的运算规则为:

    1
    2
    3
    4
    or 结果1+0=1
    or 结果1+1=2 真(非0即为真)
    or 结果0+1=1
    or 结果0+0=0
  • 结论:or(||)也称为,它的两端表达式的结果都是0时才为假,不为0就是真。相当于对前后表达式结果取并集。

5.2. 逻辑操作符实践示例

范例:[]里的操作符错用&&等逻辑运算符示例:

1
2
3
4
5
6
7
8
[root@theshu ~]# f1=/etc/rc.local ; f2=/etc/services
[root@theshu ~]# echo -ne "$f1 $f2\n"
/etc/rc.local /etc/services
[root@theshu ~]# [ -f "$f1" && -f "$f2" ] && echo 1 || echo 0
-bash: [: missing `]' #<==这是错误语法,[]中不能用&&或||
0
[root@theshu ~]# [ -f "$f1" ] && [ -f "$f2" ] && echo 1 || echo 0
1 #<==如果在[]中想使用&&,则这样用

总结前面已讲过的内容:

  • -a-o逻辑操作符号需要用于[]
  • &&||逻辑操作符号可用于[[]](())中,也可以在外部连接多个[]
  • 注意,在[][[]]的两端及比较符号的两端,必须要有空格,但是对于(())不需要。
  • 提示:[]中使用-a-o更常见,[[]]中使用&&||不常见,使用&&||连接两个[]的多表达式判断也不常见。

范例:系统启动脚本中有关[[]]的用法和与或非判断的使用案例

  • 在操作系统中,[[]]的用法不是很多,并且大多数情况都用于与通配符匹配的场景。
  • 这里不得不通过大海捞针的方法(遍历/etc.init.d/下的所有的脚本)来查找[[]]的用法:

    1
    2
    3
    4
    5
    6
    [root@theshu ~]# for n in `ls /etc/init.d/*`;do egrep -wn "\[\[ " $n&&echo $n;done
    50: if [[ $route == *" via "* ]] ; then
    75: if ! [[ "$SYSLOGADDR" =~ $IPv4_regex ]] && ! [[ "$SYSLOGADDR" =~ $IPv6_regex ]]; then
    80: if [[ $? -eq 2 ]]; then
    84: if [[ $? -ne 0 ]]; then
    /etc/init.d/netconsole
  • 由上述遍历可见,[[]]的普通应用场景不多,但在[[]]通配符匹配的场景下,其它测试表达式无法替代,因此,如果需要通配符或正则匹配就用[[]]

5.3. 逻辑操作符企业案例

范例:打印选择菜单,按照选项一键安装不同的Web服务。

示例菜单:

1
2
3
4
5
# sh menu.sh
1.[install lamp]
2.[install lnmp]
3.[exit]
Pls input the num you want:

要求:

  1. 当用户输入1时,输出”start installing lamp”提示,然后执行/server/scripts/lamp.sh,输出”lamp is installed”,并退出脚本,此为工作中所用的lamp一键安装脚本。
  2. 当用户输入2时,输出”start installing lnmp”提示,然后执行/server/scripts/lnmp.sh,输出”lnmp is installed”,并退出脚本,此为工作中所用的lnmp一键安装脚本。
  3. 当输入3时,退出当前菜单及脚本。
  4. 当输入任何其他字符时,给予提示”Input error”后退出脚本。
  5. 对执行的脚本进行相关的条件判断,例如:脚本文件是否存在,是否可执行等的判断。

打印简单的所选菜单示例1:

1
2
3
4
5
6
7
#<==前面的准备工作
# mkdir -p /server/scripts
# cd /server/scripts
# echo "echo lamp is installed" > lamp.sh
# echo "echo lnmp is installed" > lnmp.sh
# chmod +x lnmp.sh lamp.sh
#<==以上步骤模拟lamp和lnmp的安装脚本及过程。

下面是正式脚本的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/bin/bash
path=/server/scripts #<==定义脚本路径
[ ! -d "$path" ] && mkdir $path -p #<==条件表达式判断,如果目录不存在,则创建目录
#menu
cat <<END #<==利用cat命令打印选择菜单,这里也可以用select语句打印选择菜单
1.[install lamp]
2.[install lnmp]
3.[exit]
Pls input the num you want:
END
read num #<==接受用户选择的数字
expr $num + 1 &> /dev/null #判断石佛为整数
[ $? -ne 0 ] && {
echo "the num you input must be (1|2|3)"
exit 1
}
[ "$num" = "1" ] && {
echo "start installing LAMP."
sleep 2;
[ -x "$path/lamp.sh" ] || {
echo "$path/lamp.sh does not exist or can not be exec."
exit 1
}
$path/lamp.sh #<==执行脚本安装脚本,工作中建议用source $path/lamp.sh替代
#source $path/lamp.sh #<==脚本中执行脚本,使用source比sh或不加解释器等更好一些
exit $?
}
[ $num -eq 2 ] && {
echo "start installing LNMP."
sleep 2;
[ -x "$path/lnmp.sh" ] || {
echo "$path/lnmp.sh does not exist or can not be exec."
exit 1
}
$path/lnmp.sh
#source $path/lnmp.sh
exit $?
}
[ $num -eq 3 ] && {
echo bye.
exit 3
}
#这里有三种用户的输入不等于1、2或3的综合用法
[[ ! $num =~ [1-3] ]] && { #<==[[]]的正则匹配方法
echo "the num you input must be (1|2|3)"
echo "Input ERROR"
exit 4
}

提示:这里关于判断用户的输入是否等于1、2或3的用法共给出了三种。请一定要重视。使用菜单时还可以用select语句,不过还是cat的方法更常用,也更简单。

6. 测试表达式test、[]、[[]]、(()) 的区别总结

测试表达式的语法比较复杂且容易混淆,对于初学者,一定要个自己设定个知识边界。下表中列出了测试表达式[][[]](())test的区别:

测试表达式符号 [] test [[]] (())
边界是否需要空格 需要 需要 需要 不需要
逻辑操作符 !-a-o !-a-o !&&双竖线 !&&双竖线
整数比较操作符 -eq-gt
lt -ge
-le
-eq-gt
lt-ge
-le
-eq-gtlt
-ge-le
=><>=<=
=><>=<=
字符串比较操作符 ===!= ===!= ===!= ===!=
是否支持通配符匹配 不支持 不支持 支持 不支持

提示:普通的读者学习Shell编程主要是为了解决工作中的问题,因此无需掌握全部的语法,建议只用推荐的[]的用法,对其他的语法了解杰克,当有需要时,可以翻看资料或查阅bash文档man bash,以及对应命令man test的帮助。


OK

0%